package com.mstar.qs.common;

import java.io.IOException;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import net.sf.json.xml.XMLSerializer;

import com.mstar.qs.common.io.XmlUtil;
import com.mstar.qs.common.json.JsonException;

public class Json {

	private static String JSON_NODE_OBJECT = "object";
	private static String JSON_NODE_ARRAY = "array";
	private static String JSON_NODE_STRING = "string";
	private static String JSON_NODE_NUMBER = "number";
	private static String JSON_DATA_TYPE_INDATOR = "type";
	private static String JSON_DEFAULT_ELEMENT_NAME = "e";

	private static final String pe = "/";

	private final String original;
	private final JSONObject jo;

	private static String jsonroot = "json";

	/**
	 * Builde a ObjectJson instance with specified json string.
	 * 
	 * @param json
	 */
	public Json(String json) {
		this.original = json;
		jo = JSONObject.fromObject(json);
	}

	/**
	 * Obtain original string consisting of this instance.
	 * 
	 * @return
	 */
	public String original() {
		return original;
	}

	/**
	 * retrieve a value of specified attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public String attribute(String attribute) {
		try {
			return jo.getString(attribute);
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * Retrieve value of sub module's attribute specified by the passed argument
	 * attribute
	 * 
	 * @param module
	 * @param attribute
	 * @return
	 */
	public String attributeOfModule(String module, String attribute) {
		try {
			JSONObject n = jo.getJSONObject(module);
			if (n != null)
				return n.get(attribute).toString();
			else
				return null;
		} catch (JSONException e) {
			return null;
		}

	}

	/**
	 * retrieve integer value of the attribute specified by parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Integer attributeInt(String attribute) {
		try {
			return jo.getInt(attribute);
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve long value of the attribute specified by parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Long attributeLong(String attribute) {
		try {
			return jo.getLong(attribute);
		} catch (JSONException e) {
			return null;
		}

	}

	/**
	 * retrieve boolean value of the attribute specified by parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Boolean attributeBoolean(String attribute) {
		try {
			return jo.getBoolean(attribute);
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve double value of the attribute specified by parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Double attributeDouble(String attribute) {
		try {
			return jo.getDouble(attribute);
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a ObjectJson instance value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Json attributeObjectJson(String attribute) {
		try {
			String s = jo.getString(attribute);
			return new Json(s);
		} catch (JSONException e) {
			return null;
		}

	}

	/**
	 * retrieve a array filled with string value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public String[] array(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			String[] re = new String[size];
			for (int i = 0; i < size; i++) {
				re[i] = ja.getString(i);
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a array filled with integer value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public int[] arrayInt(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			int[] re = new int[size];
			for (int i = 0; i < size; i++) {
				re[i] = ja.getInt(i);
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a array filled with long value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public long[] arrayLong(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			long[] re = new long[size];
			for (int i = 0; i < size; i++) {
				re[i] = ja.getLong(i);
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a array filled with long value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public boolean[] arrayBoolean(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			boolean[] re = new boolean[size];
			for (int i = 0; i < size; i++) {
				re[i] = ja.getBoolean(i);
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a array filled with double value of the attribute specified by
	 * parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public double[] arrayDouble(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			double[] re = new double[size];
			for (int i = 0; i < size; i++) {
				re[i] = ja.getDouble(i);
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve a array filled with ObjectJson instance value of the attribute
	 * specified by parameter attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Json[] arrayObjectJson(String attribute) {
		try {
			JSONArray ja = jo.getJSONArray(attribute);
			int size = ja.size();
			if (size == 0)
				return null;
			Json[] re = new Json[size];
			for (int i = 0; i < size; i++) {
				re[i] = new Json(ja.getString(i));
			}
			return re;
		} catch (JSONException e) {
			return null;
		}
	}

	/**
	 * retrieve any object with specified attribute
	 * 
	 * @param attribute
	 * @return
	 */
	public Object get(String attribute) {
		return jo.get(attribute);
	}

	public Object[] retrieveSubObject(Json oj, String moduleName,
			String attribute) throws JsonException {
		try {
			Object o = oj.get(moduleName);
			if (o instanceof JSONObject)
				return new Object[] { ((JSONObject) o).get(attribute) };
			else {
				JSONArray ja = (JSONArray) o;
				int size = ja.size();
				Object[] v = new Object[size];
				for (int i = 0; i < size; i++) {
					Object t = ja.get(i);
					if (!(t instanceof JSONObject))
						throw new JsonException(
								"Cannot match the specified module wiht module name");
					JSONObject to = (JSONObject) t;

					v[i] = to.get(attribute);
				}
				return v;
			}
		} catch (JSONException e) {
			throw new JsonException(e);
		}
	}

	/**
	 * Convert JSON String to XML entry.
	 * 
	 * @param jsonStr
	 * @return
	 * @throws JsonException
	 */
	public static Entry json2xml(String jsonStr) throws JsonException {
		XMLSerializer se = new XMLSerializer();
		//
		JSON json = JSONSerializer.toJSON(jsonStr);
		se.setRootName("json");
		String xml = se.write(json);
		try {
			return XmlUtil.parse(xml);

		} catch (Throwable e1) {
			throw new JsonException();
		}
	}

	/**
	 * Trim head and tail empty string.
	 * 
	 * @param str
	 * @return
	 */
	private static String validateString(String s) {
		if (s.endsWith("/"))
			s = s.substring(0, s.length() - 1);

		if (s.indexOf("/") == 0)
			s = s.substring(1);
		if (s.length() == 0)
			throw new IllegalArgumentException(s);
		return s;
	}

	/**
	 * retrieve value
	 * 
	 * @param json
	 * @param xpath
	 * @return
	 * @throws JsonException
	 */
	public static String valueCross(String json, String xpath)
			throws JsonException {

		String path = validateString(xpath);

		Entry e = json2xml(json);
		try {
			return valueCross(e, path);
		} catch (JsonException e1) {
			throw new JsonException(e1.getCause() + xpath + ". \t" + json, e1);
		}
	}

	private static String valueCross(Entry root, String xpath)
			throws JsonException {
		Entry te;
		try {
			te = lastNode(root, xpath);
		} catch (Exception e) {
			throw new JsonException();
		}
		if (te == null)
			return null;
		if ("number".equals(te.attribute("type"))
				|| "string".equals(te.attribute("type")))
			return te.getValue();
		else
			return null;

	}

	public static Entry lastNode(Entry root, String xpath) {
		String p[] = xpath.split("/");
		Entry te = root;
		for (int c = 0; c < p.length; c++) {
			te = nextNode(te, p[c]);
		}
		return te;
	}

	private static Entry nextNode(Entry e, String node) {
		Entry ce = e;
		while (ce != null && "array".equals(ce.attribute("class")))
			ce = ce.retrieveSubEntry("e");

		return ce == null ? null : ce.retrieveSubEntry(node);

	}

	public static int elementSizeCross(String json, String xpath)
			throws JsonException {
		json = json.substring(json.indexOf("{"));
		String path = validateString(xpath);
		Entry e = json2xml(json);
		try {
			return elementSizeCross(e, path);
		} catch (Exception e1) {
			throw new JsonException(e1.getCause() + xpath + ". \t" + json, e1);
		}
	}

	private static int elementSizeCross(Entry root, String xpath)
			throws SAXException, IOException, ParserConfigurationException {
		Entry te = lastNode(root, xpath);
		if (te != null) {
			if ("array".equals(te.attribute("class"))) {
				List<Entry> se = te.retrieveSubEntries("e");
				if (se == null)
					return -1;
				return se.size();
			} else {

				if (te.pre() != null
						&& "object".equals(te.pre().attribute("class")))
					return te.pre().retrieveSubEntries(te.getElementName())
							.size();
				else
					return 1;
			}
		} else {
			return 0;
		}
	}

}
